package org.graph;

import java.awt.Point;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartFrame;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.labels.StandardPieSectionLabelGenerator;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PiePlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

/**
 * Useful methods for drawing some basic and default kind of charts, through the
 * usage of the JFreeChart library. Therefore, the JFreeChart library must be
 * included in to main project. All of the methods open a new ApplicationFrame
 * (extension of javax.swing.JFrame).
 *
 * @author See <a href="the Google Code page of the easy-utils
 * library">http://code.google.com/p/easy-utils/</a> for collaborators and other
 * information.
 */
public abstract class StatsCharts {

    private static double DEFAULT_EXPLODE_PERCENTAGE = 0.20;
    private static double DEFAULT_BAR_MARGIN_PERCENTAGE = 0.10;

    /**
     * Sets a new default value percentage for the desired exploded sections.
     * Percentage is measured from 0 to 1.
     *
     * @param newVal An integer representing the new value for this percentage.
     */
    public static void setExplodePercentage(double newVal) {
        StatsCharts.DEFAULT_EXPLODE_PERCENTAGE = newVal;
    }

    /**
     * Sets a new default percentage for the margin between bars for bar charts.
     * Percentage is measured from 0 to 1.
     *
     * @param newVal An integer representing the new value for this margin.
     */
    public static void setBarMarginPercentage(double newVal) {
        StatsCharts.DEFAULT_BAR_MARGIN_PERCENTAGE = newVal;
    }

    /**
     * Draws a simple pie chart.
     *
     * @param chartName {@link String} The name for the chart.
     * @param categories {@link ArrayList} of {@link String} The represented
     * categories.
     * @param categoriesVal {@link ArrayList} of {@link Double} The
     * corresponding values.
     * @param explodeIndexes {@link ArrayList} of {@link Integer} The list with
     * the indexes of the categories to explode, if desired.
     * @param labelFormat {@link String} The codification of the label format to
     * use instead the default. Therefore, is this parameter is set to
     * <code>null</code>, the default label format will be used.
     */
    public static void drawPieChart(String chartName, ArrayList<String> categories, ArrayList<Double> categoriesVal, ArrayList<Integer> explodeIndexes, String labelFormat) {
        if (categories.size() != categoriesVal.size()) {
            return;
        }
        DefaultPieDataset dataset = new DefaultPieDataset();
        for (int i = 0; i < categories.size(); i++) {
            dataset.setValue(categories.get(i), categoriesVal.get(i));
        }
        JFreeChart chart = ChartFactory.createPieChart(chartName, dataset, true, true, false);
        PiePlot plot = (PiePlot) chart.getPlot();
        try {
            for (int i = 0; i < explodeIndexes.size(); i++) {
                plot.setExplodePercent(categories.get(explodeIndexes.get(i)), StatsCharts.DEFAULT_EXPLODE_PERCENTAGE);
            }
        } catch (IndexOutOfBoundsException ex) {
            Logger.getLogger(StatsCharts.class.getName()).log(Level.SEVERE, null, ex);
            return;
        } catch (NullPointerException ex) {
            //Not asked for section exploding.
        }
        if (labelFormat != null) {
            plot.setLabelGenerator(new StandardPieSectionLabelGenerator(labelFormat));
        }
        ChartFrame frame = new ChartFrame(chartName, chart);
        frame.pack();
        frame.setVisible(true);
    }

    /**
     * Draws a simple pie chart by using default label format.
     *
     * @param chartName {@link String} The name for the chart.
     * @param categories {@link ArrayList} of {@link String} The represented
     * categories.
     * @param categoriesVal {@link ArrayList} of {@link Double} The
     * corresponding values.
     * @param explodeIndexes {@link ArrayList} of {@link Integer} The list with
     * the indexes of the categories to explode, if desired.
     */
    public static void drawPieChart(String chartName, ArrayList<String> categories, ArrayList<Double> categoriesVal, ArrayList<Integer> explodeIndexes) {
        StatsCharts.drawPieChart(chartName, categories, categoriesVal, explodeIndexes, null);
    }

    /**
     * Draws a simple pie chart without exploding any sections and using default
     * label format.
     *
     * @param chartName {@link String} The name for the chart.
     * @param categories {@link ArrayList} of {@link String} The represented
     * categories.
     * @param categoriesVal {@link ArrayList} of {@link Double} The
     * corresponding values.
     */
    public static void drawPieChart(String chartName, ArrayList<String> categories, ArrayList<Double> categoriesVal) {
        StatsCharts.drawPieChart(chartName, categories, categoriesVal, null, null);
    }

    /**
     * Draws a simple pie chart without exploding any sections.
     *
     * @param chartName {@link String} The name for the chart.
     * @param categories {@link ArrayList} of {@link String} The represented
     * categories.
     * @param categoriesVal {@link ArrayList} of {@link Double} The
     * corresponding values.
     * @param labelFormat {@link String} The codification of the label format to
     * use instead the default. Therefore, is this parameter is set to
     * <code>null</code>, the default label format will be used.
     */
    public static void drawPieChart(String chartName, ArrayList<String> categories, ArrayList<Double> categoriesVal, String labelFormat) {
        StatsCharts.drawPieChart(chartName, categories, categoriesVal, null, labelFormat);
    }

    /**
     * Draws a simple bar chart.
     *
     * @param chartName {@link String} The name for the chart.
     * @param domainAxis {@link String} The name for the domain axis.
     * @param rangeAxis {@link String} The name for the range axis.
     * @param bars {@link ArrayList} of {@link String} An ArrayList<String>
     * object containing each one of the bars.
     * @param signals {@link ArrayList} of {@link String} The names for each one
     * of the set of bars.
     * @param categoriesVal {@link ArrayList} of {@link ArrayList} of {@link Double}
     * The data to print.
     * @param plotOrientation A boolean representing the orientation. If
     * <value>true</value>, the bars are displayed vertically. Otherwise, they
     * are displayed horizontally.
     */
    public static void drawBarChart(String chartName, String domainAxis, String rangeAxis, ArrayList<String> bars, ArrayList<String> signals, ArrayList<ArrayList<Double>> categoriesVal, boolean plotOrientation) {
        //Check for some bars lacking data about some categories
        for (ArrayList<Double> current : categoriesVal) {
            if (current.size() != signals.size()) {
                return;
            }
        }
        //Check for too much data for the given sets
        if (categoriesVal.size() != bars.size()) {
            return;
        }
        PlotOrientation orientation = PlotOrientation.VERTICAL;
        if (!plotOrientation) {
            orientation = PlotOrientation.HORIZONTAL;
        }
        DefaultCategoryDataset dataset = new DefaultCategoryDataset();
        for (int i = 0; i < bars.size(); i++) {
            for (int j = 0; j < signals.size(); j++) {
                dataset.addValue(categoriesVal.get(i).get(j), bars.get(i), signals.get(j));
            }
        }
        JFreeChart chart = ChartFactory.createBarChart(chartName, domainAxis, rangeAxis, dataset, orientation, true, true, false);
        CategoryPlot plot = (CategoryPlot) chart.getPlot();
        BarRenderer renderer = (BarRenderer) plot.getRenderer();
        renderer.setItemMargin(StatsCharts.DEFAULT_BAR_MARGIN_PERCENTAGE);
        ChartFrame frame = new ChartFrame(chartName, chart);
        frame.pack();
        frame.setVisible(true);
    }

    /**
     * Draws a simple bar chart, displaying bars vertically and with default
     * names for the axes
     *
     * @param chartName {@link String} The name for the chart.
     * @param bars {@link ArrayList} of {@link String} The bars.
     * @param signals An ArrayList<String> object containing names for each one
     * of the set of bars.
     * @param categoriesVal {@link ArrayList} of {@link ArrayList} of {@link Double}
     * The data corresponding to each bar in all the signals.
     */
    public static void drawBarChart(String chartName, ArrayList<String> bars, ArrayList<String> signals, ArrayList<ArrayList<Double>> categoriesVal) {
        StatsCharts.drawBarChart(chartName, "Domain", "Range", bars, signals, categoriesVal, true);
    }

    /**
     * Draws a simple chart with one or several lines
     *
     * @param chartName {@link String} The name for the chart
     * @param domainAxis {@link String} The name for the domain axis
     * @param rangeAxis {@link String} The name for the range axis
     * @param functions {@link ArrayList} of {@link ArrayList} of {@link Point}
     * The lines to draw.
     * @param functionNames {@link ArrayList} of {@link String} The names for
     * the functions
     */
    public static void drawLinesChart(String chartName, String domainAxis, String rangeAxis, ArrayList<ArrayList<Point>> functions, ArrayList<String> functionNames) {
        XYSeries serie;
        XYSeriesCollection dataset = new XYSeriesCollection();
        for (int i = 0; i < functions.size(); i++) {
            serie = new XYSeries(functionNames.get(i));
            for (Point current : functions.get(i)) {
                serie.add(current.x, current.y);
            }
            dataset.addSeries(serie);
        }
        JFreeChart chart = ChartFactory.createXYLineChart(chartName, domainAxis, rangeAxis, dataset, PlotOrientation.VERTICAL, true, true, false);
        XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) ((XYPlot) chart.getPlot()).getRenderer();
        for (int i = 0; i < functionNames.size(); i++) {
            renderer.setSeriesShapesVisible(i, true);
            renderer.setSeriesShapesFilled(i, true);
        }
        ChartFrame frame = new ChartFrame(chartName, chart);
        frame.pack();
        frame.setVisible(true);
    }

    /**
     * Draws a simple chart with one or several lines with default names for the
     * axes.
     *
     * @param chartName {@link String} The name for the chart.
     * @param functions {@link ArrayList} of {@link ArrayList} of {@link Point}
     * The lines to draw.
     * @param functionNames {@link ArrayList} of {@link String} The names for
     * the functions.
     */
    public static void drawLinesChart(String chartName, ArrayList<ArrayList<Point>> functions, ArrayList<String> functionNames) {
        StatsCharts.drawLinesChart(chartName, "Domain", "Range", functions, functionNames);
    }
}
